package net.sourceforge.perforce.core.internal;

import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.syncinfo.FolderSyncInfo;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.team.core.TeamException;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class ResourceRefresherJob extends Job {

  private static final boolean TRACE =
      getDebugOptionAsBoolean(PerforceCorePlugin.ID + "/resourcerefresherjob/trace");

  private static boolean getDebugOptionAsBoolean(String option) {
    return "true".equalsIgnoreCase(Platform.getDebugOption(option));
  }

  private static void trace(String fmt, Object... params) {
    if (TRACE
        && !Thread.currentThread().getName().equals("Perforce Background Addition Marker Manager")
    // && !Thread.currentThread().getName().equals("ModalContext")
    )// TODO get rid of all thread-conditionals
    {
      System.out.print("ResourceRefresherJob ");
      System.out.printf(fmt, params);
      System.out.print("\n executed by thread " + Thread.currentThread().getName());
      System.out.println();
    }
  }

  private final IResource[] resources;
  private final IPerforceServer server;

  public ResourceRefresherJob(IPerforceServer server, IResource[] resources) {
    super("ResourceRefresherJob " + Thread.currentThread().getId());
    this.resources = resources;
    this.server = server;
  }

  @Override
  protected IStatus run(IProgressMonitor monitor) {

    trace("START refreshResourceStates on %s resources", resources.length);
    long startTime = System.currentTimeMillis(); // TODO unneeded when not in
                                                 // trace model

    try {
      PerforceSynchronizer synchronizer = PerforceCorePlugin.getSynchronizer();
      synchronizer.beginOperation(null);
      try {
        final int progressBarScale = 50;
        final int numberOfOperationsPerFile = 4;
        monitor.beginTask("Refreshing " + resources.length + " resources", resources.length
            * progressBarScale * numberOfOperationsPerFile);

        monitor.subTask("Collecting synchronization info...");

        Map<IResource, ResourceSyncInfo> resourceStateMap =
            new HashMap<IResource, ResourceSyncInfo>();

        List<IResource> fileList = new LinkedList<IResource>();

        for (int i = 0; i < resources.length; i++) {
          if (resources[i].getType() == IResource.FILE) {
            fileList.add(resources[i]);
          }
          monitor.worked(progressBarScale);
        }

        monitor.subTask("Collecting info for " + fileList.size()
            + " files via fstat to Perforce Server");

        int batchCounter = 0;
        final int batchSize = 2000;
        Iterator<IResource> fileIterator = fileList.iterator();
        List<IResource> currentBatch = new LinkedList<IResource>();
        ResourceSyncInfo[] currentResourceSyncInfos;
        List<ResourceSyncInfo> syncInfoList = new LinkedList<ResourceSyncInfo>();
        while (fileIterator.hasNext()) {
          while ((batchCounter < batchSize) && fileIterator.hasNext()) {
            currentBatch.add(fileIterator.next());
            batchCounter++;
          }
          monitor.subTask("Inquiring Perforce Server for " + currentBatch.size() + " files");
          currentResourceSyncInfos =
              server.fstat(currentBatch.toArray(new IFile[currentBatch.size()]), null);
          monitor.worked(currentBatch.size());
          syncInfoList.addAll(Arrays.asList(currentResourceSyncInfos));
          batchCounter = 0;
          currentBatch = new LinkedList<IResource>();
        }
        final ResourceSyncInfo[] syncInfos =
            syncInfoList.toArray(new ResourceSyncInfo[syncInfoList.size()]);
        final IFile[] files = fileList.toArray(new IFile[fileList.size()]);

        for (int k = 0; k < syncInfos.length; k++) {
          if (syncInfos[k] != null && !syncInfos[k].isDeleted()) {
            resourceStateMap.put(files[k], syncInfos[k]);
          } else {
            resourceStateMap.put(files[k], null);
          }
          monitor.worked(progressBarScale);
        }

        for (Iterator<Map.Entry<IResource, ResourceSyncInfo>> it =
            resourceStateMap.entrySet().iterator(); it.hasNext();) {
          Map.Entry<IResource, ResourceSyncInfo> entry = it.next();

          IResource resource = entry.getKey();
          ResourceSyncInfo syncInfo = entry.getValue();

          if (syncInfo == null) {
            synchronizer.deleteResourceSync(resource);
          } else {
            synchronizer.setResourceSync(resource, syncInfo);
          }

          monitor.worked(progressBarScale);
        }

        Map<IContainer, FolderSyncInfo> folderStateMap =
            addAllParents(resourceStateMap, new SubProgressMonitor(monitor, fileList.size()));

        for (Iterator<Map.Entry<IContainer, FolderSyncInfo>> it =
            folderStateMap.entrySet().iterator(); it.hasNext();) {
          Map.Entry<IContainer, FolderSyncInfo> entry = it.next();

          IContainer resource = entry.getKey();
          FolderSyncInfo syncInfo = entry.getValue();

          if (syncInfo == null) {
            synchronizer.deleteFolderSync(resource);
          } else {
            synchronizer.setFolderSync(resource, syncInfo);
          }
        }
      } catch (TeamException e) {
        PerforceCorePlugin.log(e.getStatus());
      } catch (CoreException e) {
        PerforceCorePlugin.log(e.getStatus());
      } finally {
        try {
          synchronizer.endOperation(monitor);
        } catch (PerforceException e) {
          PerforceCorePlugin.log(e.getStatus());
        }

        monitor.done();
      }
    } finally {
      trace("END refreshResourceStates - completed in %s seconds", (float) (System
          .currentTimeMillis() - startTime) / 1000);
    }
    return Status.OK_STATUS;
  }

  private Map<IContainer, FolderSyncInfo> addAllParents(
      Map<IResource, ResourceSyncInfo> resourceStateMap, IProgressMonitor monitor)
      throws PerforceException, CoreException {
    trace("start addAllParents");
    try {
      Map<IContainer, FolderSyncInfo> result = new HashMap<IContainer, FolderSyncInfo>();
      for (Iterator<Map.Entry<IResource, ResourceSyncInfo>> it =
          resourceStateMap.entrySet().iterator(); it.hasNext();) {
        Map.Entry<IResource, ResourceSyncInfo> entry = it.next();

        IResource resource = entry.getKey();
        ResourceSyncInfo syncInfo = entry.getValue();
        if (syncInfo != null) {
          String depotFilename = syncInfo.getDepotFileName();

          IContainer parent = resource.getParent();
          while (parent != null && parent.getType() != IResource.ROOT
              && !result.containsKey(parent)) {
            depotFilename = depotFilename.substring(0, depotFilename.lastIndexOf('/'));

            FolderSyncInfo folderSyncInfo = new FolderSyncInfo(depotFilename);
            result.put(parent, folderSyncInfo);
            parent = parent.getParent();
          }
        } else {
          IContainer parent = resource.getParent();
          while (parent != null && parent.getType() != IResource.ROOT
              && !result.containsKey(parent)) {
            PerforceSynchronizer synchronizer = PerforceSynchronizer.getInstance();
            IResource[] children = parent.members();

            FolderSyncInfo folderSyncInfo = null;
            for (int i = 0; i < children.length && folderSyncInfo == null; i++) {
              IResource child = children[i];

              if (child.getType() == IResource.FILE) {
                ResourceSyncInfo info = synchronizer.getResourceSync(child);
                if (info != null) {
                  String depotFilename = info.getDepotFileName();
                  depotFilename = depotFilename.substring(0, depotFilename.lastIndexOf('/'));
                  folderSyncInfo = new FolderSyncInfo(depotFilename);
                }
              } else if (child.getType() == IResource.FOLDER) {
                FolderSyncInfo info = null;
                if (result.containsKey(child)) {
                  info = result.get(child);
                } else {
                  info = synchronizer.getFolderSync((IContainer) child);
                }

                if (info != null) {
                  String depotFilename = info.getDepotFileName();
                  depotFilename = depotFilename.substring(0, depotFilename.lastIndexOf('/'));
                  folderSyncInfo = new FolderSyncInfo(depotFilename);
                }
              }
            }

            result.put(parent, folderSyncInfo);
            parent = parent.getParent();
          }
        }
      }
      return result;
    } finally {
      trace("end addAllParents");
    }
  }
}
